﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using theeg.DependencyAnalysis.CommonAddin.Behaviors;
using theeg.GraphWpf.Models;
using MemberDescriptor = DependencyAnalysis.DataModel.MemberDescriptor;
using TypeDescriptor = DependencyAnalysis.DataModel.TypeDescriptor;

namespace theeg.DependencyAnalysis.CommonAddin.Models
{
    public class ProximityModel : INotifyPropertyChanged
    {
        private readonly ICommand _changeDescriptorCommand;
        public ICommand ChangeDescriptorCommand
        {
            get { return _changeDescriptorCommand; }
        }

        private IDependencyModel _dependencyModel;

        public IDependencyModel DependencyModel
        {
            get { return _dependencyModel; }
            set
            {
                if (Equals(value, _dependencyModel)) return;
                _dependencyModel = value;
                RaisePropertyChanged("DependencyModel");
            }
        }

        private IDescriptor _descriptor;
        public IDescriptor Descriptor
        {
            get { return _descriptor; }
            set
            {
                if (Equals(_descriptor, value)) return;
                _descriptor = value;
                RaisePropertyChanged("Descriptor");
            }
        }

        private GraphParameter _graphContent;
        public GraphParameter GraphContent
        {
            get { return _graphContent; }
            set
            {
                if (Equals(_graphContent, value)) return;
                _graphContent = value;
                RaisePropertyChanged("GraphContent");
            }
        }

        public ProximityModel()
        {
            _changeDescriptorCommand = new RelayCommand(x => ChangeDescriptor(x as GraphParameter));
        }

        private void BuildGraph(IDescriptor currentDescriptor, IEnumerable<KeyValuePair<IDescriptor, int>> afferences, IEnumerable<KeyValuePair<IDescriptor, int>> efferences)
        {
            var node = GraphBuilder.BuildNode<Node>(currentDescriptor);
            node.CenterX = 0;
            node.CenterY = 0;
            _parameter.Nodes.Add(node);
            BuildTransitions(node, afferences, false);
            BuildTransitions(node, efferences, true);
        }

        private void BuildTransitions(Node currentNode, IEnumerable<KeyValuePair<IDescriptor, int>> references, bool descriptorIsSource)
        {
            int index = 0;
            KeyValuePair<IDescriptor, int>[] descriptors = references.ToArray();
            int count = descriptors.Length;
            foreach (var descriptor in descriptors.OrderBy(x => x.Key.FullName))
            {
                var nodeEfferent = GraphBuilder.BuildNode<Node>(descriptor.Key);
                nodeEfferent.CenterX = descriptorIsSource ? 400 : -400;
                nodeEfferent.CenterY = (count / 2 - count + index + 1) * 60 - ((count + 1) % 2) * 30;
                _parameter.Nodes.Add(nodeEfferent);

                var transition = GraphBuilder.BuildTransition(descriptorIsSource ? currentNode : nodeEfferent,
                    descriptorIsSource ? nodeEfferent : currentNode);
                if (descriptorIsSource)
                {
                    double x1 = currentNode.Left + currentNode.Width;
                    double x2 = nodeEfferent.Left;
                    double y1 = currentNode.CenterY;
                    double y2 = nodeEfferent.CenterY;
                    transition.AddControlPoint(new Point(x1 + (x2 - x1) * 0.25, y1 + (y2 - y1) * 0.1));
                    transition.AddControlPoint(new Point(x1 + (x2 - x1) * 0.75, y1 + (y2 - y1) * 0.9));
                }
                else
                {
                    double x1 = nodeEfferent.Left + nodeEfferent.Width;
                    double x2 = currentNode.Left;
                    double y1 = nodeEfferent.CenterY;
                    double y2 = currentNode.CenterY;
                    transition.AddControlPoint(new Point(x1 + (x2 - x1) * 0.25, y1 + (y2 - y1) * 0.1));
                    transition.AddControlPoint(new Point(x1 + (x2 - x1) * 0.75, y1 + (y2 - y1) * 0.9));
                }
                if (descriptorIsSource)
                {
                    transition.TargetText = "" + descriptor.Value;
                }
                else
                {
                    transition.SourceText = "" + descriptor.Value;
                }
                transition.Magnitude = Math.Log(descriptor.Value + 1, 6);
                _parameter.Transitions.Add(transition);
                index++;
            }
        }

        private GraphParameter _parameter;
        public void ChangeDescriptor(GraphParameter parameter)
        {
            var findDependencies = FindDependencies;
            if (findDependencies != null)
            {
                findDependencies(null);
            }
            _parameter = parameter;
            var descriptor = parameter.Descriptor;
            IDictionary<IDescriptor, ISet<DependencyDescriptor>> dependencyStructureIn;
            IDictionary<IDescriptor, ISet<DependencyDescriptor>> dependencyStructureOut;
            Func<MemberDescriptor, IDescriptor> aggregation;
            if (descriptor is MemberDescriptor)
            {
                aggregation = x => x;
                dependencyStructureIn = DependencyModelConverter.GetDependencyStructureIn<MemberDescriptor>(DependencyModel);
                dependencyStructureOut = DependencyModelConverter.GetDependencyStructureOut<MemberDescriptor>(DependencyModel);
            }
            else if (descriptor is TypeDescriptor)
            {
                aggregation = x => x.ImplementingType;
                dependencyStructureIn = DependencyModelConverter.GetDependencyStructureIn<TypeDescriptor>(DependencyModel);
                dependencyStructureOut = DependencyModelConverter.GetDependencyStructureOut<TypeDescriptor>(DependencyModel);
            }
            else if (descriptor is SubmoduleDescriptor)
            {
                aggregation = x => x.ImplementingType.Submodule;
                dependencyStructureIn = DependencyModelConverter.GetDependencyStructureIn<SubmoduleDescriptor>(DependencyModel);
                dependencyStructureOut = DependencyModelConverter.GetDependencyStructureOut<SubmoduleDescriptor>(DependencyModel);
            }
            else if (descriptor is ModuleDescriptor)
            {
                aggregation = x => x.ImplementingType.Submodule.Module;
                dependencyStructureIn = DependencyModelConverter.GetDependencyStructureIn<ModuleDescriptor>(DependencyModel);
                dependencyStructureOut = DependencyModelConverter.GetDependencyStructureOut<ModuleDescriptor>(DependencyModel);
            }
            else if (descriptor is TransitionDescriptor)
            {
                var transition = (TransitionDescriptor)descriptor;

                if (findDependencies != null)
                {
                    findDependencies(transition);
                }
                return;
            }
            else
            {
                GraphContent = null;
                return;
            }
            Descriptor = dependencyStructureIn.Keys.FirstOrDefault(x => Equals(x, descriptor));
            var afferences = VerifierUtilities.AggregateDependenciesWithCount(dependencyStructureIn[descriptor], aggregation, true);
            var efferences = VerifierUtilities.AggregateDependenciesWithCount(dependencyStructureOut[descriptor], aggregation, false);

            BuildGraph(_descriptor, afferences, efferences);
            GraphContent = parameter;

        }

        public Action<TransitionDescriptor> FindDependencies { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
